#include <QImage>
#include <QDebug>
#include <math.h>
#include <QTime>

#define PI (3.141592653589793)



float **generateGaussianKernel(float variance, int dim)
{
    float **gaussianKernel;
    int i,j;

    gaussianKernel = new float*[dim];

    for (i = 0; i < dim; ++i)
    {
        gaussianKernel[i] = new float[dim];
    }


    float exponentialArg;
    int centerX=dim/2;
    int centerY=dim/2;

    for (i=-centerX;i<=centerX;i++)
    {
        for(j=-centerY;j<=centerY;j++)
        {
            exponentialArg=-(pow(i,2)+pow(j,2))/(2*pow(variance,2));
            gaussianKernel[i+centerX][j+centerY]= exp(exponentialArg)/((2*PI)*(pow(variance,2)));
        }
    }

    return gaussianKernel;

}


float **matrixConvolutionNbyN(QImage originalImage, float **kernel, int dim)
{
    int i,j;
    int rows = originalImage.width()-(dim-1);
    int cols = originalImage.height()-(dim-1);
    int iKernel,jKernel;


    float **matrixImgA = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImgA[i] = new float[cols];
    }

    float computation=0;

    int centerX=dim/2;
    int centerY=dim/2;


    for(i=centerX;i<originalImage.width()-centerX;i++)
    {
        for(j=centerY;j<originalImage.height()-centerY;j++)
        {
                for(iKernel=0;iKernel<dim;iKernel++)
                {
                    for(jKernel=0;jKernel<dim;jKernel++)
                    {
                        computation+=kernel[iKernel][jKernel]*originalImage.pixelIndex(i-centerX+iKernel,j-centerY+jKernel);
                    }
                }
                matrixImgA[i-centerX][j-centerY]=computation;
                computation=0;
        }
    }

    for (int w = 0; w < dim; w++)
        delete[] kernel[w];
    delete[] kernel;


    return matrixImgA;

}

void matrixConvolutionNbyN(QImage originalImage, float **kernel, int dim, float **matrixImgA)
{
    int i,j;
    int rows = originalImage.width()-(dim-1);
    int cols = originalImage.height()-(dim-1);
    int iKernel,jKernel;



    float computation=0;

    int centerX=dim/2;
    int centerY=dim/2;


    for(i=centerX;i<rows;i++)
    {
        for(j=centerY;j<cols;j++)
        {
                for(iKernel=0;iKernel<dim;iKernel++)
                {
                    for(jKernel=0;jKernel<dim;jKernel++)
                    {
                        computation+=kernel[iKernel][jKernel]*originalImage.pixelIndex(i-centerX+iKernel,j-centerY+jKernel);
                    }
                }

                matrixImgA[i-centerX][j-centerY]=computation;
                computation=0;
        }
    }

    for (int w = 0; w < dim; w++)
        delete[] kernel[w];
    delete[] kernel;


}



float **testImageConvolution2by2(QImage originalImage, float kernel[][2])
{
    int i,j;
    int rows=originalImage.width()-1;
    int cols = originalImage.height()-1;
    float **matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }



    float computation;
    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
                    computation=kernel[0][0]*originalImage.pixelIndex(i,j)+
                    kernel[0][1]*originalImage.pixelIndex(i,j+1)+
                    kernel[1][0]*originalImage.pixelIndex(i+1,j)+
                    kernel[1][1]*originalImage.pixelIndex(i+1,j+1);

                    matrixImg[i][j]=computation;

        }
    }


    return matrixImg;
}


QImage normalizeImage(QImage originalImage)
{
int i,j;
int min,max;
QImage normalizedImage=originalImage;

    min=originalImage.pixelIndex(0,0);
    max=originalImage.pixelIndex(0,0);
    for(i=0;i<originalImage.width();i++)
    {
        for(j=0;j<originalImage.height();j++)
        {
            if(originalImage.pixelIndex(i,j)<min)
            {
                min=originalImage.pixelIndex(i,j);
            }
            if(originalImage.pixelIndex(i,j)>max)
            {
                max=originalImage.pixelIndex(i,j);
            }

        }
    }

    float normalizedPixel;

    for(i=0;i<originalImage.width();i++)
    {
        for(j=0;j<originalImage.height();j++)
        {
            normalizedPixel=(originalImage.pixelIndex(i,j)-min)*(float)(255/(max-min));
            normalizedImage.setPixel(i,j,normalizedPixel);
        }
    }

    return normalizedImage;

}


void normalizeMatrix(float **matrixImage, int rows, int cols)
{
    float normalizedMatrix[rows][cols];
    int i,j;
    float min,max;
    float normalizedPixel,normalizingFactor;

    min=matrixImage[0][0];
    max=matrixImage[0][0];

    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {

            if(matrixImage[i][j]<min)
            {
                min=matrixImage[i][j];
            }
            if(matrixImage[i][j]>max)
            {
                max=matrixImage[i][j];
            }

        }
    }



    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {

            normalizingFactor=(float)255/(max-min);
            normalizedPixel=round((matrixImage[i][j]-min)*normalizingFactor);
            normalizedMatrix[i][j]=normalizedPixel;
        }
    }

    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            matrixImage[i][j]=normalizedMatrix[i][j];
        }
    }

}

QImage matrixToImage(float **matrixImg, int rows, int cols)
{
    QImage img(rows, cols, QImage::Format_Indexed8);

    int i,j;
    float roundedMatrixElement;

    QRgb value;

    for (int i = 0; i < 256; ++i) {
        value=qRgb(i,i,i);
        img.setColor(i, value);
    }

    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            roundedMatrixElement=round(matrixImg[i][j]);
            img.setPixel(i,j,(int)roundedMatrixElement);
        }
    }


    return img;

}


QImage imageConvolution3by3(QImage originalImage, float kernel[][3])
{
    QImage filteredImage;
    int i,j;
    int rows=originalImage.width()-2;
    int cols = originalImage.height()-2;
    float **matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }



    float computation;
    for(i=1;i<originalImage.width()-1;i++)
    {
        for(j=1;j<originalImage.height()-1;j++)
        {
                    computation=kernel[0][0]*originalImage.pixelIndex(i-1,j-1)+
                    kernel[0][1]*originalImage.pixelIndex(i-1,j)+
                    kernel[0][2]*originalImage.pixelIndex(i-1,j+1)+
                    kernel[1][0]*originalImage.pixelIndex(i,j-1)+
                    kernel[1][1]*originalImage.pixelIndex(i,j)+
                    kernel[1][2]*originalImage.pixelIndex(i,j+1)+
                    kernel[2][0]*originalImage.pixelIndex(i+1,j-1)+
                    kernel[2][1]*originalImage.pixelIndex(i+1,j)+
                    kernel[2][2]*originalImage.pixelIndex(i+1,j+1);

                    matrixImg[i-1][j-1]=round(computation);
        }
    }

    normalizeMatrix(matrixImg, rows, cols);

    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}



float  **imageConvolution5by5(QImage originalImage, float kernel[][5])
{
    int i,j;
    int rows=originalImage.width()-4;
    int cols = originalImage.height()-4;
    float **matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }



    float computation;
    for(i=2;i<originalImage.width()-2;i++)
    {
        for(j=2;j<originalImage.height()-2;j++)
        {
            computation=kernel[0][0]*originalImage.pixelIndex(i-2,j-2)+
                    kernel[0][1]*originalImage.pixelIndex(i-2,j-1)+
                    kernel[0][2]*originalImage.pixelIndex(i-2,j)+
                    kernel[0][3]*originalImage.pixelIndex(i-2,j+1)+
                    kernel[0][4]*originalImage.pixelIndex(i-2,j+2)+
                    kernel[1][0]*originalImage.pixelIndex(i-1,j-2)+
                    kernel[1][1]*originalImage.pixelIndex(i-1,j-1)+
                    kernel[1][2]*originalImage.pixelIndex(i-1,j)+
                    kernel[1][3]*originalImage.pixelIndex(i-1,j+1)+
                    kernel[1][4]*originalImage.pixelIndex(i-1,j+2)+
                    kernel[2][0]*originalImage.pixelIndex(i,j-2)+
                    kernel[2][1]*originalImage.pixelIndex(i,j-1)+
                    kernel[2][2]*originalImage.pixelIndex(i,j)+
                    kernel[2][3]*originalImage.pixelIndex(i,j+1)+
                    kernel[2][4]*originalImage.pixelIndex(i,j+2)+
                    kernel[3][0]*originalImage.pixelIndex(i+1,j-2)+
                    kernel[3][1]*originalImage.pixelIndex(i+1,j-1)+
                    kernel[3][2]*originalImage.pixelIndex(i+1,j)+
                    kernel[3][3]*originalImage.pixelIndex(i+1,j+1)+
                    kernel[3][4]*originalImage.pixelIndex(i+1,j+2)+
                    kernel[4][0]*originalImage.pixelIndex(i+2,j-2)+
                    kernel[4][1]*originalImage.pixelIndex(i+2,j-1)+
                    kernel[4][2]*originalImage.pixelIndex(i+2,j)+
                    kernel[4][3]*originalImage.pixelIndex(i+2,j+1)+
                    kernel[4][4]*originalImage.pixelIndex(i+2,j+2);

                    matrixImg[i-2][j-2]=computation;
        }
    }

    return matrixImg;
}

float **matrixImageConvolution3by3(QImage originalImage, float kernel[][3])
{
    int i,j;
    int rows=originalImage.width()-2;
    int cols = originalImage.height()-2;
    float **matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }



    float computation;
    for(i=1;i<originalImage.width()-1;i++)
    {
        for(j=1;j<originalImage.height()-1;j++)
        {
                    computation=kernel[0][0]*originalImage.pixelIndex(i-1,j-1)+
                    kernel[0][1]*originalImage.pixelIndex(i-1,j)+
                    kernel[0][2]*originalImage.pixelIndex(i-1,j+1)+
                    kernel[1][0]*originalImage.pixelIndex(i,j-1)+
                    kernel[1][1]*originalImage.pixelIndex(i,j)+
                    kernel[1][2]*originalImage.pixelIndex(i,j+1)+
                    kernel[2][0]*originalImage.pixelIndex(i+1,j-1)+
                    kernel[2][1]*originalImage.pixelIndex(i+1,j)+
                    kernel[2][2]*originalImage.pixelIndex(i+1,j+1);

                    matrixImg[i-1][j-1]=computation;
        }
    }


    return matrixImg;
}


QImage boxFilter(QImage originalImage)
{
    QImage filteredImage;
    int rows=originalImage.width()-2;
    int cols=originalImage.height()-2;
    float **resultMatrix;
    float kernelBoxFilter[3][3]={1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0};


    resultMatrix=matrixImageConvolution3by3(originalImage,kernelBoxFilter);

    filteredImage=matrixToImage(resultMatrix,rows,cols);

    return filteredImage;
}

QImage medianFilter(QImage originalImage)
{
    QImage filteredImage;
    int rows=originalImage.width()-2;
    int cols=originalImage.height()-2;
    QVector<int> elemVector;

    int medianValue=4;
    int i,j;


    float **matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }

    for(i=1;i<originalImage.width()-1;i++)
    {
        for(j=1;j<originalImage.height()-1;j++)
        {
            elemVector.append(originalImage.pixelIndex(i-1,j-1));
            elemVector.append(originalImage.pixelIndex(i-1,j));
            elemVector.append(originalImage.pixelIndex(i-1,j+1));
            elemVector.append(originalImage.pixelIndex(i,j-1));
            elemVector.append(originalImage.pixelIndex(i,j));
            elemVector.append(originalImage.pixelIndex(i,j+1));
            elemVector.append(originalImage.pixelIndex(i+1,j-1));
            elemVector.append(originalImage.pixelIndex(i+1,j));
            elemVector.append(originalImage.pixelIndex(i+1,j+1));
            qSort(elemVector);
            matrixImg[i-1][j-1]=elemVector.value(medianValue);
            elemVector.clear();
        }
    }

    //normalizeMatrix(resultMatrix, rows, cols);
    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}

QImage rankFilter(QImage originalImage, int dim)
{
    QImage filteredImage;
    int rows=originalImage.width()-2;
    int cols=originalImage.height()-2;
    QVector<int> elemVector;

    int medianValue=4;
    int i,j;



    float **matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }

    for(i=1;i<originalImage.width()-1;i++)
    {
        for(j=1;j<originalImage.height()-1;j++)
        {
            elemVector.append(originalImage.pixelIndex(i-1,j-1));
            elemVector.append(originalImage.pixelIndex(i-1,j));
            elemVector.append(originalImage.pixelIndex(i-1,j+1));
            elemVector.append(originalImage.pixelIndex(i,j-1));
            elemVector.append(originalImage.pixelIndex(i,j));
            elemVector.append(originalImage.pixelIndex(i,j+1));
            elemVector.append(originalImage.pixelIndex(i+1,j-1));
            elemVector.append(originalImage.pixelIndex(i+1,j));
            elemVector.append(originalImage.pixelIndex(i+1,j+1));
            qSort(elemVector);
            if(dim==3)
            {
                matrixImg[i-1][j-1]=(elemVector.value(medianValue)+elemVector.value(medianValue+1)+elemVector.value(medianValue-1))/3.0;
                elemVector.clear();
            }
            else if(dim==5)
            {
                matrixImg[i-1][j-1]=(elemVector.value(medianValue)+elemVector.value(medianValue+1)+elemVector.value(medianValue-1)+elemVector.value(medianValue+2)+elemVector.value(medianValue-2))/5.0;
                elemVector.clear();
            }
            else
            {
                matrixImg[i-1][j-1]=(elemVector.value(medianValue)+elemVector.value(medianValue+1)+elemVector.value(medianValue-1)+elemVector.value(medianValue+2)+elemVector.value(medianValue-2)+elemVector.value(medianValue+3)+elemVector.value(medianValue-3))/7.0;
                elemVector.clear();
            }
        }
    }

    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}

float getVarianceFromArray(QVector<int> array)
{
    float variance;
    int i;
    int numElem=array.size();
    float mean;
    float sum=0;

    for(i=0;i<numElem;i++)
    {
        sum+=array.value(i);
    }

    mean=(float)sum/numElem;

    sum=0;

    for(i=0;i<numElem;i++)
    {
        sum+=pow(array.value(i)-mean,2);
    }

    variance=(float)sum/numElem;
    return variance;


}

float getMeanFromArray(QVector<int> array)
{
    int i;
    int numElem=array.size();
    float mean;
    float sum=0;

    for(i=0;i<numElem;i++)
    {
        sum+=array.value(i);
    }

    mean=(float)sum/numElem;

    return mean;
}

QImage nagaoFilter(QImage originalImage)
{
    QImage filteredImage;
    float** matrixImg;
    int i,j;
    float currentMean;
    QVector<int> nagaoArray;
    int rows = originalImage.width()-4;
    int cols = originalImage.height()-4;

    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }
    float currentVariance,minVariance;

    for(i=2;i<originalImage.width()-2;i++)
    {
        for(j=2;j<originalImage.height()-2;j++)
        {

            //FIRST NAGAO SUBBLOCK
            nagaoArray.append(originalImage.pixelIndex(i-1,j-1));
            nagaoArray.append(originalImage.pixelIndex(i-1,j));
            nagaoArray.append(originalImage.pixelIndex(i-1,j+1));
            nagaoArray.append(originalImage.pixelIndex(i,j-1));
            nagaoArray.append(originalImage.pixelIndex(i,j));
            nagaoArray.append(originalImage.pixelIndex(i,j+1));
            nagaoArray.append(originalImage.pixelIndex(i+1,j-1));
            nagaoArray.append(originalImage.pixelIndex(i+1,j));
            nagaoArray.append(originalImage.pixelIndex(i+1,j+1));

            currentVariance=getVarianceFromArray(nagaoArray);
            currentMean=getMeanFromArray(nagaoArray);

            minVariance=currentVariance;

            nagaoArray.clear();

            //SECOND NAGAO SUBBLOCK
            nagaoArray.append(originalImage.pixelIndex(i-2,j-1));
            nagaoArray.append(originalImage.pixelIndex(i-2,j));
            nagaoArray.append(originalImage.pixelIndex(i-2,j+1));
            nagaoArray.append(originalImage.pixelIndex(i-1,j));
            nagaoArray.append(originalImage.pixelIndex(i-1,j-1));
            nagaoArray.append(originalImage.pixelIndex(i-1,j+1));
            nagaoArray.append(originalImage.pixelIndex(i,j));

            currentVariance=getVarianceFromArray(nagaoArray);
            if(currentVariance<minVariance)
            {
                minVariance=currentVariance;
                currentMean=getMeanFromArray(nagaoArray);
            }


            nagaoArray.clear();

            //THIRD NAGAO SUBBLOCK

            nagaoArray.append(originalImage.pixelIndex(i-1,j-2));
            nagaoArray.append(originalImage.pixelIndex(i-1,j-1));
            nagaoArray.append(originalImage.pixelIndex(i,j-2));
            nagaoArray.append(originalImage.pixelIndex(i,j-1));
            nagaoArray.append(originalImage.pixelIndex(i,j));
            nagaoArray.append(originalImage.pixelIndex(i+1,j-2));
            nagaoArray.append(originalImage.pixelIndex(i+1,j-1));


            currentVariance=getVarianceFromArray(nagaoArray);
            if(currentVariance<minVariance)
            {
                minVariance=currentVariance;
                currentMean=getMeanFromArray(nagaoArray);
            }


            nagaoArray.clear();



            //FOURTH NAGAO SUBBLOCK
            nagaoArray.append(originalImage.pixelIndex(i-1,j+1));
            nagaoArray.append(originalImage.pixelIndex(i-1,j+2));
            nagaoArray.append(originalImage.pixelIndex(i,j));
            nagaoArray.append(originalImage.pixelIndex(i,j+1));
            nagaoArray.append(originalImage.pixelIndex(i,j+2));
            nagaoArray.append(originalImage.pixelIndex(i+1,j+1));
            nagaoArray.append(originalImage.pixelIndex(i+1,j+2));


            currentVariance=getVarianceFromArray(nagaoArray);
            if(currentVariance<minVariance)
            {
                minVariance=currentVariance;
                currentMean=getMeanFromArray(nagaoArray);
            }


            nagaoArray.clear();

            //FIFTH NAGAO SUBBLOCK

            nagaoArray.append(originalImage.pixelIndex(i,j));
            nagaoArray.append(originalImage.pixelIndex(i+1,j-1));
            nagaoArray.append(originalImage.pixelIndex(i+1,j));
            nagaoArray.append(originalImage.pixelIndex(i+1,j+1));
            nagaoArray.append(originalImage.pixelIndex(i+2,j-1));
            nagaoArray.append(originalImage.pixelIndex(i+2,j));
            nagaoArray.append(originalImage.pixelIndex(i+2,j+1));

            currentVariance=getVarianceFromArray(nagaoArray);
            if(currentVariance<minVariance)
            {
                minVariance=currentVariance;
                currentMean=getMeanFromArray(nagaoArray);
            }


            nagaoArray.clear();

            //SIXTH NAGAO SUBBLOCK

            nagaoArray.append(originalImage.pixelIndex(i-2,j-2));
            nagaoArray.append(originalImage.pixelIndex(i-2,j-1));
            nagaoArray.append(originalImage.pixelIndex(i-1,j-2));
            nagaoArray.append(originalImage.pixelIndex(i-1,j-1));
            nagaoArray.append(originalImage.pixelIndex(i-1,j));
            nagaoArray.append(originalImage.pixelIndex(i,j-1));
            nagaoArray.append(originalImage.pixelIndex(i,j));

            currentVariance=getVarianceFromArray(nagaoArray);
            if(currentVariance<minVariance)
            {
                minVariance=currentVariance;
                currentMean=getMeanFromArray(nagaoArray);
            }


            nagaoArray.clear();

            //SEVENTH NAGAO SUBBLOCK
            nagaoArray.append(originalImage.pixelIndex(i-2,j+1));
            nagaoArray.append(originalImage.pixelIndex(i-2,j+2));
            nagaoArray.append(originalImage.pixelIndex(i-1,j));
            nagaoArray.append(originalImage.pixelIndex(i-1,j+1));
            nagaoArray.append(originalImage.pixelIndex(i-1,j+2));
            nagaoArray.append(originalImage.pixelIndex(i,j));
            nagaoArray.append(originalImage.pixelIndex(i,j+1));



            currentVariance=getVarianceFromArray(nagaoArray);
            if(currentVariance<minVariance)
            {
                minVariance=currentVariance;
                currentMean=getMeanFromArray(nagaoArray);
            }


            nagaoArray.clear();



            //EIGHT NAGAO SUBBLOCK
            nagaoArray.append(originalImage.pixelIndex(i,j-1));
            nagaoArray.append(originalImage.pixelIndex(i,j));
            nagaoArray.append(originalImage.pixelIndex(i+1,j-2));
            nagaoArray.append(originalImage.pixelIndex(i+1,j-1));
            nagaoArray.append(originalImage.pixelIndex(i+1,j));
            nagaoArray.append(originalImage.pixelIndex(i+2,j-2));
            nagaoArray.append(originalImage.pixelIndex(i+2,j-1));


            currentVariance=getVarianceFromArray(nagaoArray);
            if(currentVariance<minVariance)
            {
                minVariance=currentVariance;
                currentMean=getMeanFromArray(nagaoArray);
            }


            nagaoArray.clear();

            //NINTH NAGAO SUBBLOCK
            nagaoArray.append(originalImage.pixelIndex(i,j));
            nagaoArray.append(originalImage.pixelIndex(i,j+1));
            nagaoArray.append(originalImage.pixelIndex(i+1,j));
            nagaoArray.append(originalImage.pixelIndex(i+1,j+1));
            nagaoArray.append(originalImage.pixelIndex(i+1,j+2));
            nagaoArray.append(originalImage.pixelIndex(i+2,j+1));
            nagaoArray.append(originalImage.pixelIndex(i+2,j+2));

            currentVariance=getVarianceFromArray(nagaoArray);
            if(currentVariance<minVariance)
            {
                minVariance=currentVariance;
                currentMean=getMeanFromArray(nagaoArray);
            }


            nagaoArray.clear();

            matrixImg[i-2][j-2]=currentMean;

        }
    }


    normalizeMatrix(matrixImg, rows, cols);
    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;



}

QImage sharpeningFilter(QImage originalImage)
{
    QImage filteredImage;
    float kernelBoxFilter[3][3]={1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0,1.0/9.0};
    float kernelBoxFIlter2Centered[3][3]={0,0,0,0,2.0,0,0,0,0};
    float kernelSharpeningFilter[3][3];
    int i=0,j=0;
    int rows=originalImage.width()-2;
    int cols=originalImage.height()-2;
    float **resultMatrix;

    for(i=0;i<3;i++)
    {
        for(j=0;j<3;j++)
        {
            kernelSharpeningFilter[i][j]=kernelBoxFIlter2Centered[i][j]-kernelBoxFilter[i][j];
        }

    }
    resultMatrix=matrixImageConvolution3by3(originalImage,kernelSharpeningFilter);

    normalizeMatrix(resultMatrix, rows, cols);
    filteredImage=matrixToImage(resultMatrix,rows,cols);

    return filteredImage;
}

float **sobelVerticalFilter(QImage originalImage)
{
    QImage filteredImage;
    float kernelSobelVerticalFilter[3][3]={-1,-2,-1,0,0,0,1,2,1};
    float **resultMatrix;

    resultMatrix=matrixImageConvolution3by3(originalImage,kernelSobelVerticalFilter);
    return resultMatrix;

    //return filteredImage;
}

float **sobelHorizontalFilter(QImage originalImage)
{

    float kernelSobelHorizontalFilter[3][3]={-1,0,1,-2,0,2,-1,0,1};
    float **resultMatrix;

    resultMatrix=matrixImageConvolution3by3(originalImage,kernelSobelHorizontalFilter);
    return resultMatrix;

}

QImage laplacianFilter(QImage originalImage)
{
    QImage filteredImage;
    int rows=originalImage.width()-4;
    int cols=originalImage.height()-4;
    float **resultMatrix;
    float kernelLaplacianFilter[5][5]={0,0,-1,0,0,0,-1,-2,-1,0,-1,-2,16,-2,-1,0,-1,-2,-1,0,0,0,-1,0,0};


    resultMatrix=imageConvolution5by5(originalImage,kernelLaplacianFilter);
    normalizeMatrix(resultMatrix, rows, cols);

    filteredImage=matrixToImage(resultMatrix,rows,cols);

    return filteredImage;
}

QImage gaussianFilter(QImage originalImage)
{
    QImage filteredImage;
    int rows=originalImage.width()-4;
    int cols=originalImage.height()-4;
    float **resultMatrix;
    float kernelGaussianFilter[5][5]={0.03,0.013,0.022,0.013,0.003,0.0013,0.059,0.097,0.059,0.013,0.022,0.097,0.159,0.097,0.022,0.013,0.059,0.097,0.059,0.013,0.003,0.013,0.022,0.013,0.003};

    resultMatrix=imageConvolution5by5(originalImage,kernelGaussianFilter);


    filteredImage=matrixToImage(resultMatrix,rows,cols);

    return filteredImage;
}


QImage sobelModuleFilter(QImage originalImage)
{
    QImage filteredImage;
    float** matrixImgVertical;
    float** matrixImgHorizontal;
    float** matrixImg;
    int i,j;
    int rows = originalImage.width()-2;
    int cols = originalImage.height()-2;

    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }


    matrixImgVertical=sobelVerticalFilter(originalImage);
    matrixImgHorizontal=sobelHorizontalFilter(originalImage);


    float module;

    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            module=sqrt(pow(matrixImgVertical[i][j],2)+pow(matrixImgHorizontal[i][j],2));
            matrixImg[i][j]=module;
        }
    }


    normalizeMatrix(matrixImg, rows, cols);

    filteredImage=matrixToImage(matrixImg,rows,cols);
    return filteredImage;
}

QImage sobelPhaseFilter(QImage originalImage)
{
    float **matrixImgVertical;
    float **matrixImgHorizontal;

    float** matrixImg;
    int i,j;
    int rows = originalImage.width()-2;
    int cols = originalImage.height()-2;

    QImage filteredImage;

    float phase;

    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }

    matrixImgVertical=sobelVerticalFilter(originalImage);
    matrixImgHorizontal=sobelHorizontalFilter(originalImage);

    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            phase=atan2(matrixImgVertical[i][j],matrixImgHorizontal[i][j]);
            //phaseDegree=(phase*180)/3.14159265359;
            matrixImg[i][j]=phase;
        }
    }

    normalizeMatrix(matrixImg, rows, cols);
    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}


QImage robertsModuleFilter(QImage originalImage)
{
    QImage filteredImage;
    float **filteredImageMainDiag;
    float **filteredImageSecDiag;
    float  **matrixImg;
    int rows=originalImage.width()-1;
    int cols=originalImage.height()-1;
    float kernelRobertsMainDiagFilter[2][2]={1,0,0,-1};
    float kernelRobertsSecDiagFilter[2][2]={0,1,-1,0};
    int i,j;

    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }

    float module;


    filteredImageMainDiag=testImageConvolution2by2(originalImage,kernelRobertsMainDiagFilter);
    filteredImageSecDiag=testImageConvolution2by2(originalImage,kernelRobertsSecDiagFilter);

    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            module=sqrt(pow(filteredImageMainDiag[i][j],2)+pow(filteredImageSecDiag[i][j],2));
            matrixImg[i][j]=module;

        }
    }


    normalizeMatrix(matrixImg, rows, cols);
    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}

QImage robertsPhaseFilter(QImage originalImage)
{
    float **filteredImageMainDiag;
    float **filteredImageSecDiag;
    float  **matrixImg;
    int i,j;
    int rows = originalImage.width()-1;
    int cols = originalImage.height()-1;

    float kernelRobertsMainDiagFilter[2][2]={1,0,0,-1};
    float kernelRobertsSecDiagFilter[2][2]={0,1,-1,0};

    QImage filteredImage;

    float phase;


    filteredImageMainDiag=testImageConvolution2by2(originalImage,kernelRobertsMainDiagFilter);
    filteredImageSecDiag=testImageConvolution2by2(originalImage,kernelRobertsSecDiagFilter);

        matrixImg = new float*[rows];
        for (i = 0; i < rows; ++i)
        {
            matrixImg[i] = new float[cols];
        }

        for(i=0;i<rows;i++)
        {
            for(j=0;j<cols;j++)
            {
                phase=atan2(filteredImageSecDiag[i][j],filteredImageMainDiag[i][j])+(PI/4);
                matrixImg[i][j]=phase;
            }
        }

    normalizeMatrix(matrixImg, rows, cols);
    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}


float **isotropicVerticalFilter(QImage originalImage)
{
    float **resultMatrix;
    float kernelIsotropicVerticalFilter[3][3]={-1,0,1,-sqrt(2),0,sqrt(2),-1,0,1};

    resultMatrix=matrixImageConvolution3by3(originalImage,kernelIsotropicVerticalFilter);

    return resultMatrix;
}

float **isotropicHorizontalFilter(QImage originalImage)
{
    float **resultMatrix;
    float kernelIsotropicHorizontalFilter[3][3]={1,sqrt(2),1,0,0,0,-1,-sqrt(2),-1};

    resultMatrix=matrixImageConvolution3by3(originalImage,kernelIsotropicHorizontalFilter);

    return resultMatrix;
}


QImage isotropicModuleFilter(QImage originalImage)
{
    float **filteredImageVertical=isotropicVerticalFilter(originalImage);
    float **filteredImageHorizontal=isotropicHorizontalFilter(originalImage);
    int rows=originalImage.width()-2;
    int cols=originalImage.height()-2;
    QImage filteredImage;

    float module=0;
    int i,j;
    float **matrixImg;

    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }

    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            module=sqrt(pow(filteredImageVertical[i][j],2)+pow(filteredImageHorizontal[i][j],2));
            matrixImg[i][j]=module;
        }
    }


    normalizeMatrix(matrixImg, rows, cols);
    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}

QImage isotropicPhaseFilter(QImage originalImage)
{
    float **filteredImageVertical=isotropicVerticalFilter(originalImage);
    float **filteredImageHorizontal=isotropicHorizontalFilter(originalImage);
    float **matrixImg;
    QImage filteredImage;
    int rows=originalImage.width()-2;
    int cols=originalImage.height()-2;
    float phase=0;

    int i,j;

    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }

    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            phase=atan2(filteredImageVertical[i][j],filteredImageHorizontal[i][j]);
            matrixImg[i][j]=phase;
        }
    }

    normalizeMatrix(matrixImg, rows, cols);
    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}

float **prewittVerticalFilter(QImage originalImage)
{
    float **resultImg;
    float kernelPrewittVerticalFilter[3][3]={-1,-1,-1,0,0,0,1,1,1};

    resultImg=matrixImageConvolution3by3(originalImage,kernelPrewittVerticalFilter);

    return resultImg;
}

float **prewittHorizontalFilter(QImage originalImage)
{
    float **resultImg;
    float kernelPrewittHorizontalFilter[3][3]={-1,0,1,-1,0,1,-1,0,1};

    resultImg=matrixImageConvolution3by3(originalImage,kernelPrewittHorizontalFilter);

    return resultImg;
}


QImage prewittModuleFilter(QImage originalImage)
{
    float **filteredImageVertical=prewittVerticalFilter(originalImage);
    float **filteredImageHorizontal=prewittHorizontalFilter(originalImage);


    QImage filteredImage;

    float module=0;
    int i,j;

    float **matrixImg;

    int rows=originalImage.width()-2;
    int cols=originalImage.height()-2;

    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }


    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            module=sqrt(pow(filteredImageVertical[i][j],2)+pow(filteredImageHorizontal[i][j],2));
            matrixImg[i][j]=module;
        }
    }

    normalizeMatrix(matrixImg, rows, cols);
    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}

QImage prewittPhaseFilter(QImage originalImage)
{
    float **filteredImageVertical=prewittVerticalFilter(originalImage);
    float **filteredImageHorizontal=prewittHorizontalFilter(originalImage);

    QImage filteredImage;

    double phase=0;
    int i,j;
    float **matrixImg;

    int rows=originalImage.width()-2;
    int cols=originalImage.height()-2;


    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }


    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            phase=atan2(filteredImageVertical[i][j],filteredImageHorizontal[i][j]);
            matrixImg[i][j]=phase;
        }
    }

    normalizeMatrix(matrixImg, rows, cols);
    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}


void rotateKernel(float kernel[3][3])
{
    float rotatedNewKernel[3][3];
    int i,j;
    rotatedNewKernel[0][0]=kernel[0][1];
    rotatedNewKernel[0][1]=kernel[0][2];
    rotatedNewKernel[0][2]=kernel[1][2];
    rotatedNewKernel[1][0]=kernel[0][0];
    rotatedNewKernel[1][1]=kernel[1][1];
    rotatedNewKernel[1][2]=kernel[2][2];
    rotatedNewKernel[2][0]=kernel[1][0];
    rotatedNewKernel[2][1]=kernel[2][0];
    rotatedNewKernel[2][2]=kernel[2][1];

    for(i=0;i<3;i++)
    {
        for(j=0;j<3;j++)
        {
            kernel[i][j]=rotatedNewKernel[i][j];
        }
    }
}


QImage kirschFilter(QImage originalImage)
{
    float **filteredMatrix;
    float **finalFilteredImage;
    QImage filteredImage;

    int rows=originalImage.width()-2;
    int cols=originalImage.height()-2;
    float **matrixImg;
    int i,j,k;

    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }

    float kernelKirschFilter[3][3]={5,5,5,-3,0,-3,-3,-3,-3};

    filteredMatrix=matrixImageConvolution3by3(originalImage,kernelKirschFilter);
    finalFilteredImage=matrixImageConvolution3by3(originalImage,kernelKirschFilter);
    rotateKernel(kernelKirschFilter);
    for(i=0;i<7;i++)
    {
        filteredMatrix=matrixImageConvolution3by3(originalImage,kernelKirschFilter);
        for(j=0;j<rows;j++)
        {
            for(k=0;k<cols;k++)
            {
                if(filteredMatrix[j][k]>finalFilteredImage[j][k])
                {
                    finalFilteredImage[j][k]=filteredMatrix[j][k];
                }
            }
        }
        rotateKernel(kernelKirschFilter);
    }

    normalizeMatrix(finalFilteredImage, rows, cols);
    filteredImage=matrixToImage(finalFilteredImage,rows,cols);

    return filteredImage;
}

float **thresholdMatrix(float **matrixToThreshold, int rows, int cols)
{
    int i,j;
    float **thresholdedMatrix;
    thresholdedMatrix = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        thresholdedMatrix[i] = new float[cols];
    }


    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            if(matrixToThreshold[i][j]>=0)
            {
                thresholdedMatrix[i][j]=255;
            }
            else
            {
                thresholdedMatrix[i][j]=0;
            }
        }
    }

    return thresholdedMatrix;

}

QImage threeNineFilter(QImage originalImage)
{
    float **filteredMatrix;
    float **sumMatrix;
    float **finalFilteredImage;
    QImage filteredImage;

    int rows=originalImage.width()-2;
    int cols=originalImage.height()-2;
    float **matrixImg;
    int i,j,k;
    float kernelBlockSum[3][3]={1,1,1,1,1,1,1,1,1};
    float kernelThreeNineFilter[3][3]={1,1,1,0,0,0,0,0,0};

    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }

    sumMatrix=matrixImageConvolution3by3(originalImage,kernelBlockSum);

    filteredMatrix=matrixImageConvolution3by3(originalImage,kernelThreeNineFilter);
    finalFilteredImage=matrixImageConvolution3by3(originalImage,kernelThreeNineFilter);
    rotateKernel(kernelThreeNineFilter);
    for(i=0;i<7;i++)
    {
        filteredMatrix=matrixImageConvolution3by3(originalImage,kernelThreeNineFilter);
        for(j=0;j<rows;j++)
        {
            for(k=0;k<cols;k++)
            {
                if(filteredMatrix[j][k]>finalFilteredImage[j][k])
                {
                    finalFilteredImage[j][k]=filteredMatrix[j][k];
                }
            }
        }
        rotateKernel(kernelThreeNineFilter);
    }

    for(j=0;j<rows;j++)
    {
        for(k=0;k<cols;k++)
        {
            if(sumMatrix[j][k]==0)
            {
                matrixImg[j][k]=0;
            }
            else
            {
                matrixImg[j][k]=1.5*( (finalFilteredImage[j][k]/sumMatrix[j][k])-0.333 );
            }
        }
    }

    normalizeMatrix(matrixImg, rows, cols);
    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}


QImage DoGFilter(QImage originalImage, float firstVar, float secondVar)
{
    QImage filteredImage,thresholdedImage;
    float **maxVarKernel;
    float **minVarKernel;
    float **diffKernel;
    float **convolutedMatrix;
    float **thresholdedMatrix;


    float maxVar,minVar;
    int kernelMaxDim;
    int i,j;
    if(firstVar>=secondVar)
    {
        maxVar=firstVar;
        minVar=secondVar;
    }
    else
    {
        maxVar=secondVar;
        minVar=firstVar;
    }

    float floatKernelMaxDim=6.0*maxVar;

    if((int)floatKernelMaxDim%2==0)
    {
        kernelMaxDim=(int)floatKernelMaxDim+1;
    }
    else
    {
        kernelMaxDim=(int)floatKernelMaxDim;
    }


    int rows = originalImage.width()-(kernelMaxDim-1);
    int cols = originalImage.height()-(kernelMaxDim-1);

    diffKernel = new float*[kernelMaxDim];
    for (i = 0; i < kernelMaxDim; ++i)
    {
        diffKernel[i] = new float[kernelMaxDim];
    }


    maxVarKernel=generateGaussianKernel(maxVar, kernelMaxDim);
    minVarKernel=generateGaussianKernel(minVar, kernelMaxDim);

    for(i=0;i<kernelMaxDim;i++)
    {
        for(j=0;j<kernelMaxDim;j++)
        {
            diffKernel[i][j]=maxVarKernel[i][j]-minVarKernel[i][j];
        }
    }

    convolutedMatrix=matrixConvolutionNbyN(originalImage,diffKernel,kernelMaxDim);
    //normalizeMatrix(convolutedMatrix,rows,cols);

    thresholdedMatrix=thresholdMatrix(convolutedMatrix,rows,cols);

    //thresholdedImage=matrixToImage(thresholdedMatrix,rows,cols);

    float **sumMatrix;//=matrixImageConvolution3by3(thresholdedImage,unitaryKernel);

    sumMatrix = new float*[rows-2];
    for (i = 0; i <rows-2; ++i)
    {
        sumMatrix[i] = new float[cols-2];
    }
    int center;

    for(i=1;i<rows-1;i++)
    {
        for(j=1;j<cols-1;j++)
        {
            center=thresholdedMatrix[i][j];
            if(thresholdedMatrix[i-1][j-1]!=center || thresholdedMatrix[i-1][j]!=center || thresholdedMatrix[i-1][j+1]!=center || thresholdedMatrix[i][j-1]!=center || thresholdedMatrix[i][j+1]!=center || thresholdedMatrix[i+1][j-1]!=center || thresholdedMatrix[i+1][j]!=center || thresholdedMatrix[i+1][j+1]!=center)
            {
                sumMatrix[i-1][j-1]=255;
            }
            else
            {
                sumMatrix[i-1][j-1]=0;
            }
        }
    }

    filteredImage=matrixToImage(sumMatrix,rows-2,cols-2);

    return filteredImage;

}

double randomValue()
{
    double randNumber = ((double) rand() / (RAND_MAX));
    return randNumber;
}

int randomValueFromMinToMax(int min, int max)
{
    int randNumber=(rand()%(max-min))+min;
    return randNumber;
}

double nextGaussian(double *nextNextGaussian, bool *haveNextNextGaussian)
{

   if (*haveNextNextGaussian)
   {
     *haveNextNextGaussian = false;
     return *nextNextGaussian;
   }
   else
   {
     double v1, v2, s;
     do
     {
       v1 = 2 * randomValue() - 1;
       v2 = 2 * randomValue() - 1;
       s = v1 * v1 + v2 * v2;
     }

     while (s >= 1 || s == 0);
     double multiplier = sqrt(-2 * log(s)/s);
     *nextNextGaussian = v2 * multiplier;
     *haveNextNextGaussian = true;
     return v1 * multiplier;
   }
}


QImage uniformNoise(QImage originalImage, int maxNoiseIntensity)
{
    QImage filteredImage;
    QTime time = QTime::currentTime();
    qsrand((uint)time.msec());



    float **matrixImg;
    int rows=originalImage.width();
    int cols=originalImage.height();
    int i,j;
    double computation=0;
    double computationWithNoise=0;

    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }

    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            double randNumber = ((double) rand() / (RAND_MAX));
            computation= 2*maxNoiseIntensity*(randNumber-0.5);
            computationWithNoise=originalImage.pixelIndex(i,j)+computation;
            if(computationWithNoise>=255)
            {
                matrixImg[i][j]=255;
            }

            else if(computationWithNoise<=0)
            {
                matrixImg[i][j]=0;
            }
            else
            {
                matrixImg[i][j]=computationWithNoise;
            }

        }
    }

    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}


QImage gaussianNoise(QImage originalImage, float mean, float variance)
{
    QImage filteredImage;
    double nextNextGaussian=0;
    bool haveNextNextGaussian = false;

    float **matrixImg;
    int rows=originalImage.width();
    int cols=originalImage.height();
    int i,j;
    double computation=0;
    double computationWithNoise=0;

    matrixImg = new float*[rows];
    for (i = 0; i < rows; ++i)
    {
        matrixImg[i] = new float[cols];
    }

    for(i=0;i<rows;i++)
    {
        for(j=0;j<cols;j++)
        {
            computation= (nextGaussian(&nextNextGaussian,&haveNextNextGaussian) * sqrt(variance)) + mean;
            computationWithNoise=originalImage.pixelIndex(i,j)+computation;

            if(computationWithNoise>=255)
            {
                matrixImg[i][j]=255;
            }

            else if(computationWithNoise<=0)
            {
                matrixImg[i][j]=0;
            }
            else
            {
                matrixImg[i][j]=computationWithNoise;
            }

        }
    }

    filteredImage=matrixToImage(matrixImg,rows,cols);

    return filteredImage;
}

QImage saltPepperNoise(QImage originalImage, int percentage)
{
    QImage filteredImage=originalImage;

    float floatedPercentage= (float) percentage/100;

    int rows=originalImage.width();
    int cols=originalImage.height();
    int totalPixels=rows*cols;
    int modifiedPixels= (int) totalPixels*floatedPercentage;
    int i;
    int xCasual, yCasual;
    int flipCoin;

    for(i=0;i<modifiedPixels;i++)
    {
        xCasual=randomValueFromMinToMax(0,rows);
        yCasual=randomValueFromMinToMax(0,cols);
        flipCoin=randomValueFromMinToMax(0,2);
        if(flipCoin==0)
        {
            filteredImage.setPixel(xCasual,yCasual,0);
        }
        else
        {
            filteredImage.setPixel(xCasual,yCasual,255);
        }
    }

    return filteredImage;
}

QImage impulseNoise(QImage originalImage, int percentage)
{
    QImage filteredImage=originalImage;

    float floatedPercentage= (float) percentage/100;

    int rows=originalImage.width();
    int cols=originalImage.height();
    int totalPixels=rows*cols;
    int modifiedPixels= (int) totalPixels*floatedPercentage;
    int i;
    int xCasual, yCasual;

    for(i=0;i<modifiedPixels;i++)
    {
        xCasual=randomValueFromMinToMax(0,rows);
        yCasual=randomValueFromMinToMax(0,cols);
        filteredImage.setPixel(xCasual,yCasual,255);
    }

    return filteredImage;
}
